Skip to content

feat(metadata-protocol): enforce package namespace prefix for Studio-authored packages#2694

Merged
os-zhuang merged 2 commits into
mainfrom
feat/studio-namespace-prefix
Jul 8, 2026
Merged

feat(metadata-protocol): enforce package namespace prefix for Studio-authored packages#2694
os-zhuang merged 2 commits into
mainfrom
feat/studio-namespace-prefix

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Why

Per the protocol (ADR-0028 current-state contract; manifest.zod namespace), every object.name in a package must carry the package's namespace prefix (crm_account, showcase_project). defineStack() enforces this at compile time via validateNamespacePrefix.

Studio / runtime-authored packages bypassed it entirely: they are created without a namespace at all, and neither the runtime create path nor the publish path ran the check. So an admin building a package in Studio got objects with bare, collision-prone names (two packages could each publish leave_request → silent table/route collision, exactly the risk ADR-0028/ADR-0048 flag).

Found by dogfooding: a Studio-built package com.example.leave persisted namespace: null, and its object leave_request published with zero prefix validation.

What

Two runtime changes, both in @objectstack/metadata-protocol:

  • installPackage derives a default namespace from the package id (com.example.leaveleave) when the manifest declares none, and persists it on the single manifest object shared by the in-memory registry and the durable sys_packages row. An explicitly declared namespace always wins — HotCRM's crm (which differs from its id's last segment hotcrm) is untouched.
  • publishPackageDrafts rejects any object draft whose name lacks the <ns>_ prefix, before promoting anything (the publish is atomic), with an actionable message. Namespace-less legacy packages are grandfathered — mirroring defineStack, the rule is not invented at enforcement time; the default is derived+persisted once at install.

The per-object check and the id→namespace derivation are extracted into @objectstack/spec/kernel (validateObjectNamespacePrefix, deriveNamespaceFromPackageId) as the single source shared by defineStack and the runtime publish path, so the two enforcement points cannot drift. validateNamespacePrefix in stack.zod.ts is refactored onto the shared helper (behavior-preserving).

Verification

Unit/regression (all green):

  • spec: namespace-prefix.test.ts (10 new) + stack.test.ts namespace suite — 95 passed.
  • objectql: protocol-publish-package-drafts.test.ts (+3: reject / allow-prefixed / grandfather), protocol-install-package.test.ts (+2: derive / explicit-wins), plus install/lifecycle/collision suites — no regressions.

Live end-to-end on a running showcase app (rebuild + restart):

  • New package com.example.hr → namespace auto-derived to hr
  • Object ticket → publish rejected: Object 'ticket' is missing the package namespace prefix. Rename it to 'hr_ticket' (namespace = 'hr').
  • Mixed good+bad drafts → whole atomic batch rejected, nothing published ✅
  • Compliant pto_request under namespace pto → publishes ✅
  • Grandfathered: pre-existing namespace-less com.example.leave still reads; showcase unaffected ✅

Follow-up (objectui, separate)

Studio's "新建软件包" dialog should surface the namespace (default-derived, editable) and "新建对象" should auto-prefix the identifier, so authors never hit the publish-time rejection. Filed separately.

🤖 Generated with Claude Code

…authored packages

The protocol requires every object.name to carry its package's manifest.namespace
prefix (crm_account); defineStack enforces this at compile time. Studio/runtime-
authored packages never took that path and were created without a namespace at
all, so the rule was silently inert and objects published with bare, collision-
prone names (e.g. two packages could each publish `leave_request`).

- installPackage: derive a default namespace from the package id when the
  manifest declares none (com.example.leave → leave) and persist it on the
  manifest; an explicit namespace always wins (HotCRM's crm is untouched).
- publishPackageDrafts: reject any object draft missing the <ns>_ prefix before
  promoting anything (atomic), with an actionable "rename to ..." message.
  Namespace-less packages are grandfathered, mirroring defineStack.

The per-object check and id→namespace derivation are extracted to
@objectstack/spec/kernel as the single source shared by defineStack and the
runtime publish path, so the two enforcement points cannot drift.

Found by dogfooding package-first authoring: a Studio-built package
(com.example.leave) persisted namespace=null, so its object leave_request had no
prefix and published with zero validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 8, 2026 4:52pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec.

99 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/profiles.mdx (via @objectstack/spec)
  • content/docs/permissions/roles.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Additive-only: registers validateObjectNamespacePrefix and
deriveNamespaceFromPackageId (both new exports from @objectstack/spec/kernel)
in the frozen API-surface snapshot. Consistent with the minor bump already in
the changeset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@os-zhuang os-zhuang merged commit fc7e7f7 into main Jul 8, 2026
17 checks passed
@os-zhuang os-zhuang deleted the feat/studio-namespace-prefix branch July 8, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant